//
//  WebViewController.m
//  MyProject
//
//  Created by yun on 2018/4/28.
//  Copyright © 2018年 yun. All rights reserved.
//

#import "WebViewController.h"
#import <WebKit/WebKit.h>

@interface WebViewController ()<WKScriptMessageHandler,WKNavigationDelegate,WKUIDelegate>

@property (nonatomic,strong) WKWebView *webView;

@end

@implementation WebViewController

/*  首先，在注册要被JavaScript调用的方法时需要设置代理，在不需要时需要将代理移除，WKUserContentController中也提供了移除这个代理的方法，如果不移除，将会造成WebView不能释放。方法如下：
 
 注册一个监听方法
 - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
 移除一个方法的监听
 - (void)removeScriptMessageHandlerForName:(NSString *)name;
 
 注入一个JavaScript抽象对象
 - (void)addUserScript:(WKUserScript *)userScript;
 移除所有注入
 - (void)removeAllUserScripts;
 
 */


- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self setupWebView];
}

/*WKWebView常用属性及方法
 
 //设置导航代理
 @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
 //设置UI代理
 @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
 //导航列表
 @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
 //通过url加载网页视图
 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
 //通过文件加载网页视图
 - (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0);
 //通过HTML字符串加载网页视图
 - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
 //通过data数据加载网页视图
 - (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);
 //渲染导航列表中的某个网页节点
 - (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
 //网页标题
 @property (nullable, nonatomic, readonly, copy) NSString *title;
 //网页的url
 @property (nullable, nonatomic, readonly, copy) NSURL *URL;
 //网页是否正在加载中
 @property (nonatomic, readonly, getter=isLoading) BOOL loading;
 //加载进度 可以监听这个属性的值配合UIProgressView来设计进度条
 @property (nonatomic, readonly) double estimatedProgress;
 //是否全部是安全连接
 @property (nonatomic, readonly) BOOL hasOnlySecureContent;
 //证书列表
 @property (nonatomic, readonly, copy) NSArray *certificateChain;
 //是否可以回退
 @property (nonatomic, readonly) BOOL canGoBack;
 //是否可以前进
 @property (nonatomic, readonly) BOOL canGoForward;
 //回退网页
 - (nullable WKNavigation *)goBack;
 //前进网页
 - (nullable WKNavigation *)goForward;
 //刷新网页
 - (nullable WKNavigation *)reload;
 //忽略缓存的刷新
 - (nullable WKNavigation *)reloadFromOrigin;
 //停止加载
 - (void)stopLoading;
 //执行JavaScript代码
 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
 //是否允许右滑返回手势
 @property (nonatomic) BOOL allowsBackForwardNavigationGestures;
 
 //如果未设置自定义用户代理字符串，则自定义用户代理字符串或nil
 @property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));;
 //一个布尔值，指示是否允许在此WKWebView中的任何链接使用链接预览。在Mac和iOS上，默认值为YES。
 @property (nonatomic) BOOL allowsLinkPreview API_AVAILABLE(macosx(10.11), ios(9.0));
 
 */



/*WKBackForwardList类为导航管理的网页列表类，其中属性方法意义如下：
 
 //当前所在的网页节点
 @property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *currentItem;
 //前进的一个网页节点
 @property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *forwardItem;
 //回退的一个网页节点
 @property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *backItem;
 //获取某个index的网页节点
 - (nullable WKBackForwardListItem *)itemAtIndex:(NSInteger)index;
 //获取回退的节点数组
 @property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *backList;
 //获取前进的节点数组
 @property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *forwardList;
 
 */

/*在WebKit中，网页节点被抽象成为了WKBackForwardListItem类，这个类中封装的属性如下：
 
 //当前节点的URL
 @property (readonly, copy) NSURL *URL;
 //当前节点的标题
 @property (nullable, readonly, copy) NSString *title;
 //创建此WebView的初始URL
 @property (readonly, copy) NSURL *initialURL;
 */

- (void)setupWebView{
    self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:[self webViewConfiguration]];
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://219.139.44.218:4001/h22web1/WebWFMobile/newa/Show.aspx?isclose=1&appname=T_P_APP_YHZGTZS&appdataid=84"]]];
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;
    [self.view addSubview:self.webView];
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
}

- (WKWebViewConfiguration *)webViewConfiguration{
    // 参数配置
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    
    /* ①进程池
     WKProcessPool类中没有暴露任何属性和方法，配置为同一个进程池的WebView会共享数据，例如Cookie、用户凭证等，开发者可以通过编写管理类来分配不同维度的WebView在不同进程池中。
     */
    WKProcessPool *pool = [[WKProcessPool alloc] init];
    config.processPool = pool;
    
    /*②偏好设置
     minimumFontSize:最小字体大小 当将javaScriptEnabled属性设置为NO时，可以看到明显的效果
     javaScriptEnabled:设置是否支持javaScript 默认是支持的
     javaScriptCanOpenWindowsAutomatically:设置是否允许不经过用户交互由javaScript自动打开窗口
     */
    WKPreferences *preference = [[WKPreferences alloc] init];
    preference.minimumFontSize = 0;
    preference.javaScriptEnabled = YES;
    preference.javaScriptCanOpenWindowsAutomatically = YES;
    config.preferences = preference;
    
    
    /*③native与JavaScript的交互管理
     WKUserContentController专门用来管理native与JavaScript的交互行为，addScriptMessageHandler:name:方法来注册要被js调用的方法名称，之后再JavaScript中使用window.webkit.messageHandlers.name.postMessage()方法来像native发送消息，支持OC中字典，数组，NSNumber等原生数据类型，JavaScript代码中的name要和上面注册的相同。在native代理的回调方法中，会获取到JavaScript传递进来的消息
     */
    WKUserContentController *userController = [[WKUserContentController alloc] init];
    //设置处理代理并且注册要被js调用的方法名称
    //addScriptMessageHandler:name:有两个参数，第一个参数是userContentController的代理对象，第二个参数是JS里发送postMessage的对象。添加一个脚本消息的处理器,同时需要在JS中添加，window.webkit.messageHandlers.<name>.postMessage(<messageBody>)才能起作用。
    [userController addScriptMessageHandler:self name:@"name"];
    
    //js注入，注入一个测试方法。一般是一些配置
    NSString *javaScriptSource = @"function userFunc(){window.webkit.messageHandlers.name.postMessage( {\"name\":\"HS\"})}";
    // WKUserScript:用于进行JavaScript注入。
    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];// forMainFrameOnly:NO(全局窗口)，yes（只限主窗口）
    [userController addUserScript:userScript];
    config.userContentController = userController;
    
    /*④WebKit框架采用其本身的缓存框架，WKWebsiteDataStore类用来处理数据的存储，其中属性和方法如下：
     //获取默认的存储器 此存储器为持久性的会被写入磁盘
     + (WKWebsiteDataStore *)defaultDataStore;
     //获取一个临时的存储器
     + (WKWebsiteDataStore *)nonPersistentDataStore;
     //存储器是否是临时的
     @property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
     //所有可以存储的类型
     + (NSSet<NSString *> *)allWebsiteDataTypes;
     */
    if (@available(iOS 9.0, *)) {
        config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
    } else {
        // Fallback on earlier versions
    }
    
    //设置是否将网页内容全部加载到内存后再渲染
    config.suppressesIncrementalRendering = NO;
    //设置HTML5视频是否允许网页播放 设置为NO则会使用本地播放器
    config.allowsInlineMediaPlayback =  YES;
    
    if (@available(iOS 9.0, *)) {
        //设置是否允许airPlay播放
        config.allowsAirPlayForMediaPlayback = YES;
        
        //设置视频是否需要用户手动播放  设置为NO则会允许自动播放
        config.requiresUserActionForMediaPlayback = NO;
        
        //设置是否允许画中画技术 在特定设备上有效
        config.allowsPictureInPictureMediaPlayback = YES;
        
        //设置请求的User-Agent信息中应用程序名称 iOS9后可用
        config.applicationNameForUserAgent = @"HS";
    } else {
        // Fallback on earlier versions
    }
    
    //设置选择模式 是按字符选择 还是按模块选择
    /*
     typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
     //按模块选择
     WKSelectionGranularityDynamic,
     //按字符选择
     WKSelectionGranularityCharacter,
     } NS_ENUM_AVAILABLE_IOS(8_0);
     */
    config.selectionGranularity = WKSelectionGranularityCharacter;
    
    return config;
}

#pragma mark -- 交互方式
/*
  WebKit中的native与JavaScript的交互主要有4类。
 
 1.JavaScript调用native方法
 这种方式是由WKUserContentController注册，并在代理方法中实现的。
 
 2.native调用JavaScript方法
 这种方式通过WKWebView直接调用evaluteJavaScript:completionHandler:方法来实现。
 
 3.将JavaScript代码注入
 这种方式可以在网页中注入一些自定义的JavaScript代码，也可以注入自定义的方法，再使用evaluteJavaScript:completionHandler:来调用方法。JavaScript代码的注入也是通过WKUserContentController来完成的，使用addUserScript:方法来注入JavaScript，其中需要通过WKUserScript类来生成要注入的对象，这个类使用如下方法来进行实例化：
 
 source为要注入的js代码
 WKUserScriptInjectionTime设置注入的时机
 forMainFrameOnly参数设置是否只在主页面注入
 typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
 //原js代码运行前注入
 WKUserScriptInjectionTimeAtDocumentStart,
 //原js代码运行后注入
 WKUserScriptInjectionTimeAtDocumentEnd
 } NS_ENUM_AVAILABLE(10_10, 8_0);
 
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
 
 
 4.通过WKUIDelegate来交互
 这种方式主要用于相应JavaScript中的弹出框，后面会详细介绍这个协议。
 
 */


#pragma mark -- WKScriptMessageHandler
// addScriptMessageHandler:name:方法触发的代理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    // 获取js传来的消息
    /*WKScriptMessage一些属性
     body:传递的消息主体
     webView:传递消息的WebView
     frameInfo:传递消息的WebView当前页面对象
     name:消息名称
     */
}

#pragma mark -- WKNavagationDelegate

/*
 决定是否响应网页的某个动作，例如加载，回退，前进，刷新等，在这个方法中，必须执行decisionHandler()代码块，并将是否允许这个活动执行在block中进行传入
 
 WKNavigationAction是网页动作的抽象化，其中封装了许多行为信息，后面会介绍
 WKNavigationActionPolicy为开发者回执，枚举如下：
 typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
 取消此次行为
 WKNavigationActionPolicyCancel,
 允许此次行为
 WKNavigationActionPolicyAllow,
 } NS_ENUM_AVAILABLE(10_10, 8_0);

 */

-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
    decisionHandler(WKNavigationActionPolicyAllow);
}


/*
 接收到数据后是否允许执行渲染
 
 其中，WKNavigationResponse为请求回执信息
 WKNavigationResponsePokicy为开发者回执，枚举如下：
 typedef NS_ENUM(NSInteger, WKNavigationResponsePolicy) {
 取消渲染
 WKNavigationResponsePolicyCancel,
 允许渲染
 WKNavigationResponsePolicyAllow,
 } NS_ENUM_AVAILABLE(10_10, 8_0);
 */
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    decisionHandler(WKNavigationResponsePolicyAllow);
}


//需要响应身份验证时调用 同样在block中需要传入用户身份凭证
-(void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
    //用户身份信息
    NSURLCredential *newCred = [NSURLCredential credentialWithUser:@""
                                                          password:@""
                                                       persistence:NSURLCredentialPersistenceNone];
    // 为 challenge 的发送方提供 credential
    [[challenge sender] useCredential:newCred
           forAuthenticationChallenge:challenge];
    completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}


//=====================下面这个协议方法用于监听流程=========================================
//页面加载启动时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
    
}
//当主机接收到的服务重定向时调用
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
    
}
//内容到达主机时调用
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
    
}
//主页加载完成时调用
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    
}
//提交发生错误时调用
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    
}
//主页数据加载发生错误时调用
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error{
    
}
//进程被终止时调用
-(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
    
}

#pragma mark -- WKUIDelegate

//创建新的webView时调用的方法
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
//
//    NSLog(@"createWebViewWithConfiguration  request     %@",navigationAction.request);
//    if (!navigationAction.targetFrame.isMainFrame) {
//        [webView loadRequest:navigationAction.request];
//    }
//    if (navigationAction.targetFrame == nil) {
//        [webView loadRequest:navigationAction.request];
//    }
    return nil;
    
}
//关闭webView时调用的方法
-(void)webViewDidClose:(WKWebView *)webView{
    
}


//下面这些方法是交互JavaScript的方法
//JavaScript调用alert方法后回调的方法 message中为alert提示的信息 必须要在其中调用completionHandler()
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    NSLog(@"%@",message);
    completionHandler();
}
//JavaScript调用confirm方法后回调的方法 confirm是js中的确定框，需要在block中把用户选择的情况传递进去
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    NSLog(@"%@",message);
    completionHandler(YES);
}
//JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
    NSLog(@"%@",prompt);
    completionHandler(@"123");
}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/



@end
